package dataStructures.index;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;

//import javax.xml.parsers.SAXParser;
//import javax.xml.parsers.SAXParserFactory;

import org.apache.xerces.parsers.SAXParser;

import org.xml.sax.helpers.DefaultHandler;

import utils.MyLogger;
import utils.TimeCounter;
import xml.RouteToIdIndexHandler;

import dataStructures.LoggedDast;

/*
 * this class will hold the mapping from a route to all nodes which can be reached
 * from the root via this route
 */
public class RouteToIdIndex extends LoggedDast implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1832716344260530867L;

	private LinkedHashMap<ArrayList<Integer>, ArrayList<Integer>> _routeToIdMap;
	
	private HashMap<String,Integer> _labelToLabelIdMap;
	

	public RouteToIdIndex() 
	{
		_routeToIdMap = new LinkedHashMap<ArrayList<Integer>, ArrayList<Integer>>(); 
			//new HashMap<String, String>();
		
		_labelToLabelIdMap = new HashMap<String, Integer>();
	}
	
	public boolean LoadXml(String xmlPath)
	{
		TimeCounter timeCounter = new TimeCounter();

		try
		{

			AddLogEntry("Starting building RouteToIdIndex for " + xmlPath);
			//SAXParserFactory factory = SAXParserFactory.newInstance();
			//SAXParser saxParser = factory.newSAXParser();
			SAXParser saxParser = new SAXParser();
			DefaultHandler handler = new RouteToIdIndexHandler(_routeToIdMap,_labelToLabelIdMap);
			saxParser.setContentHandler(handler);
			//saxParser.parse(xmlPath, handler);
			saxParser.parse(xmlPath);
		 	
		}
		catch (Exception e)
		{
			AddLogEntry("Error while building index:  " + e.getMessage());
			return false;
		}
		AddLogEntry("Index build complete with " + String.valueOf(_routeToIdMap.size()) + " routes in " + String.valueOf(timeCounter.Toc()) + " ms");
		return true;
	}
	
	public ArrayList<String> DeserializedRoute(ArrayList<Integer> serializedRoute)
	{
		HashMap<Integer, String> labelIdToLabel = new HashMap<Integer, String>();
		ArrayList<String> deserializedRoute = new ArrayList<String>();
		
		for(String key:_labelToLabelIdMap.keySet())
		{
			labelIdToLabel.put(_labelToLabelIdMap.get(key), key);
		}
		
		
		for(int labelId : serializedRoute)
		{
			deserializedRoute.add(labelIdToLabel.get(labelId));
		}
		
		
		
		return deserializedRoute;
	}
	
	public void PrintRouts()
	{
		/*for(String k:_routeToIdMap.keySet())
		{
			System.out.println("Route: " + DeserializedRoute(k) + " NodesList: " + _routeToIdMap.get(k));
		}*/
		
		for(ArrayList<Integer> route : _routeToIdMap.keySet())
		{
			System.out.println(route.toString() + " " + " " + DeserializedRoute(route)+ " "+ _routeToIdMap.get(route).toString());
		}
	}
	
	public ArrayList<Integer> SerializeRoute(ArrayList<String> labelsRoute)
	{
		ArrayList<Integer> serializedRoute = new ArrayList<Integer>();
		
		for(String label : labelsRoute)
		{
			serializedRoute.add(_labelToLabelIdMap.get(label));
		}
		
		return serializedRoute;
	}
	public ArrayList<Integer> GetRouteNodesId(ArrayList<String> route)
	{
		return _routeToIdMap.get(SerializeRoute(route));
	}
	
	public ArrayList<Integer> GetSerializedRouteNodesId(ArrayList<Integer> route)
	{
		return _routeToIdMap.get(route);
	}
	
	public Iterator<ArrayList<Integer>> RoutesIterator()
	{
		return _routeToIdMap.keySet().iterator();
	}
	
	
	
	
	
	//methods for serialization
	@SuppressWarnings("unchecked")
	public static RouteToIdIndex Load(InputStream stream) throws Exception
    {
		ObjectInputStream s = new ObjectInputStream(stream);
		RouteToIdIndex index = new RouteToIdIndex();
		index._labelToLabelIdMap = (HashMap<String,Integer>)s.readObject();
		s.readObject();
		int numberOfRoutes = (Integer)s.readObject();
		
		for(int r = 0 ; r < numberOfRoutes ; ++r)
		{
			index._routeToIdMap.put((ArrayList<Integer>)s.readObject(), (ArrayList<Integer>)s.readObject());
		}
		
		//index._routeToIdMap = (LinkedHashMap<ArrayList<Integer>, ArrayList<Integer>>)s.readObject();
		/*int[] arr = (int[])s.readObject();
		

		int arrIndex = 0;
		
		while(arrIndex < arr.length)
		{
			int keyLength = arr[arrIndex++];
			ArrayList<Integer> key = new ArrayList<Integer>(keyLength);
			for(int i = 0 ; i < keyLength;++i,++arrIndex)
			{
				key.add(arr[arrIndex]);
			}
			
			int valLength = arr[arrIndex++];
			ArrayList<Integer> val = new ArrayList<Integer>(valLength);
			for(int i = 0 ; i < valLength ; ++i,++arrIndex)
			{
				val.add(arr[arrIndex]);
			}
			
			index._routeToIdMap.put(key, val);
			
		}
		*/
		return index;
    }
	
	//methods for serialization
	public static RouteToIdIndex Load(InputStream stream,MyLogger logger) throws Exception
    {
		TimeCounter timeCounter = new TimeCounter();
		logger.AddEntry("loading RouteToIdIndex from stream");
		RouteToIdIndex index = Load(stream);
		logger.AddEntry("loading complete in " + String.valueOf(timeCounter.Toc()) + " ms");
		return index;
    }

	private int GetNumberOfElements()
	{
		int n = 0;
		for(ArrayList<Integer> key:_routeToIdMap.keySet())
		{
			n+=(key.size()+1);
			n+=(_routeToIdMap.get(key).size()+1);
		}
		
		return n;
	}
	
	private int SerializeArr(int[] arr,int index,ArrayList<Integer> arrList)
	{
		arr[index++] = arrList.size();
		
		for(int val:arrList)
		{
			arr[index++] = val;
		}
		
		return arrList.size()+1;
	}
	
	private int[] SerializeLabelToLabelIDMap()
	{
		//ArrayList<Integer> arr;
		int[] arr = new int[GetNumberOfElements()];
		int index = 0;
		for(ArrayList<Integer> key:_routeToIdMap.keySet())
		{
			index+=SerializeArr(arr, index, key);
			index+=SerializeArr(arr, index, _routeToIdMap.get(key));
		}
		
		return arr;
	}
	
	private int GetNumberOfNodes()
	{
		int n = 0;
		for(ArrayList<Integer> key:_routeToIdMap.keySet())
		{
			n += _routeToIdMap.get(key).size();
		}
		
		return n;
	}
	public void Store(OutputStream stream) throws Exception
	{
	 
		
		TimeCounter timeCounter = new TimeCounter();
		AddLogEntry("storing RouteToIdIndex from stream");
		ObjectOutputStream s = new ObjectOutputStream(stream);
		
		
		s.writeObject(_labelToLabelIdMap);
		s.writeObject(GetNumberOfNodes());
		s.writeObject(_routeToIdMap.size());
		
		
		
		for(ArrayList<Integer> key:_routeToIdMap.keySet())
		{
			s.writeObject(key);
			s.writeObject(_routeToIdMap.get(key));
		}
		
		//s.writeObject(_routeToIdMap);
		//TODO check if this work!!!!!
		//s.writeObject(SerializeLabelToLabelIDMap());
		//s.writeObject(this);
		s.flush();
		AddLogEntry("storing complete in " + String.valueOf(timeCounter.Toc()) + " ms");
	}
	
	
	
	/**
	 * this is a basic tester for this index
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception
	{

		RouteToIdIndex idx = new RouteToIdIndex();
		MyLogger logger = new MyLogger("D:\\development\\dblab\\dblp.log");
		idx.AttachLogger(logger);
		
		idx.LoadXml("D:\\development\\dblab\\xmlDocs\\dblp.xml");
		FileOutputStream fos = new FileOutputStream("D:\\development\\dblab\\dblp.idx");
		idx.Store(fos);
		
		fos.close();
	}
}
